/* 2017/05/05 */
package org.sample.springboot.converter;

import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class Converter {
	
	/**
	 * Filter type  before scan to improve performance.
	 *
	 */
	public static interface Filter {
		/**
		 * Check whether specified type is in scope.
		 * 
		 * @param type
		 * @return true if type is in scope, otherwise false.
		 */
		boolean filter(Type type);
	}

	private static final Logger LOGGER = LoggerFactory.getLogger(Converter.class);
	
	private Filter filter;
	
	private Map<Type, Set<TypeMember>> convertMap = new HashMap<>();
	
	protected boolean filter(Type type) {
		if (filter != null) {
			return filter.filter(type);
		} else {
			return true;
		}
	}

	protected void record(Type type) {
		LOGGER.debug("Record " + type.getTypeName());
		
		if (!convertMap.containsKey(type)) {
			convertMap.put(type, new LinkedHashSet<>());
		}
	}

	protected boolean isRecorded(Type type) {
		return convertMap.containsKey(type);
	}

	protected boolean hasConvertMember(Type type) {
		if (convertMap.containsKey(type)) {
			return !convertMap.get(type).isEmpty();
		} else {
			return false;
		}
	}

	protected void addConvertMember(Type type, TypeMember member) {
		LOGGER.debug("Add " + member.toString() + " of " + type.getTypeName());
		
		if (convertMap.containsKey(type)) {
			convertMap.get(type).add(member);
		} else {
			Set<TypeMember> members = new LinkedHashSet<>();
			members.add(member);

			convertMap.put(type, members);
		}
	}

	protected Set<TypeMember> getConvertMembers(Type type) {
		Set<TypeMember> targets = convertMap.get(type);
		if (targets == null) {
			targets = Collections.emptySet();
		}
		return targets;
	}

	public void scan(Type... types) {
		for (Type type : types) {
			scan(type);
		}
	}

	public abstract boolean scan(Type type);

	public void convert(Object obj) {
		Objects.requireNonNull(obj);

		Class<?> clazz = obj.getClass();
		convert(obj, clazz);
	}

	/**
	 * Convert object according to the specified type, not runtime type.
	 * 
	 * @param obj
	 * @param clazz
	 */
	public abstract void convert(Object obj, Type type);

	public Filter getChecker() {
		return filter;
	}

	public void setChecker(Filter checker) {
		this.filter = checker;
	}

}

